﻿using System; // Provides fundamental classes and base types.
using System.Collections.Generic; // Provides classes for collections, like IEnumerable<T>.
using System.Diagnostics; // Provides classes for interacting with system processes.
using System.IO; // Provides classes for file and directory I/O.
using System.Linq; // Provides LINQ (Language-Integrated Query) capabilities.
using System.Text.RegularExpressions; // Provides classes for working with regular expressions to parse text.
using System.Threading; // Provides classes for multi-threaded programming, like SemaphoreSlim and CancellationToken.
using System.Threading.Tasks; // Provides classes for asynchronous programming.

namespace DefenderUI
{
    /// <summary>
    /// This class encapsulates all logic for interacting with the Windows Defender
    /// command-line tool (MpCmdRun.exe). It is responsible for finding the scanner,
    /// running signature updates, and performing file scans.
    /// </summary>
    public class DefenderScanner
    {
        // --- PRIVATE FIELDS ---

        // Stores the full file path to the MpCmdRun.exe executable. This is read-only
        // to ensure it's set only once in the constructor.
        private readonly string _defenderPath;

        // A constant that defines the maximum number of concurrent file scans.
        // This prevents the application from overwhelming the system with too many simultaneous processes.
        private const int MaxConcurrentScans = 4;

        // --- PUBLIC PROPERTIES ---

        /// <summary>
        /// A public property that returns true if the Defender executable was found, and false otherwise.
        /// This allows the UI (Form1) to check if scanning features can be enabled.
        /// </summary>
        public bool IsDefenderAvailable => !string.IsNullOrEmpty(_defenderPath);

        // --- CONSTRUCTOR ---

        /// <summary>
        /// The constructor for the DefenderScanner class. Its primary job is to find the
        /// location of the Defender command-line tool when a new instance is created.
        /// </summary>
        public DefenderScanner()
        {
            _defenderPath = GetDefenderPath();
        }

        // --- PRIVATE METHODS ---

        /// <summary>
        /// Searches for the MpCmdRun.exe in common system locations.
        /// </summary>
        /// <returns>The full path to the executable if found; otherwise, null.</returns>
        private string GetDefenderPath()
        {
            // Get the path to the "Program Files" directory, which can vary (e.g., C:\Program Files).
            string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);

            // Create an array of potential paths where the executable might be located.
            // This includes both the standard Program Files and the x86 version for 64-bit systems.
            string[] potentialPaths = {
                Path.Combine(programFiles, "Windows Defender", "MpCmdRun.exe"),
                Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Windows Defender", "MpCmdRun.exe")
            };

            // Use LINQ's FirstOrDefault to return the first path in the array that actually exists on the disk.
            // If none of the paths exist, it will return the default value for a string, which is null.
            return potentialPaths.FirstOrDefault(File.Exists);
        }

        /// <summary>
        /// Parses the text output from a Defender scan to determine the result.
        /// </summary>
        /// <param name="filePath">The path of the file that was scanned.</param>
        /// <param name="exitCode">The exit code returned by the MpCmdRun.exe process.</param>
        /// <param name="output">The full string output captured from the process.</param>
        /// <returns>A ScanResult object containing the parsed information.</returns>
        private ScanResult ParseDefenderOutput(string filePath, int exitCode, string output)
        {
            var result = new ScanResult { FilePath = filePath };

            // The exit code of MpCmdRun.exe indicates the outcome of the scan.
            switch (exitCode)
            {
                case 0: // An exit code of 0 means no threats were found.
                    result.Status = ScanStatus.Clean;
                    result.Message = "CLEAN";
                    break;
                case 2: // An exit code of 2 means a threat was detected.
                    result.Status = ScanStatus.ThreatFound;
                    result.Message = "THREAT";
                    // Use a regular expression to find the line containing "Threat :" and extract the threat name.
                    var match = Regex.Match(output, @"Threat\s+:(.*)", RegexOptions.IgnoreCase);
                    // If the regex match is successful, use the captured group; otherwise, provide a default name.
                    result.ThreatName = match.Success ? match.Groups[1].Value.Trim() : "Unknown Threat";
                    break;
                default: // Any other exit code is treated as an error.
                    result.Status = ScanStatus.Error;
                    result.Message = $"ERROR (Code: {exitCode})";
                    break;
            }
            return result;
        }

        // --- PUBLIC ASYNCHRONOUS METHODS ---

        /// <summary>
        /// Asynchronously initiates a signature update for Windows Defender.
        /// </summary>
        /// <param name="progress">An IProgress object used to report real-time output lines back to the UI.</param>
        /// <returns>A Task that resolves to true if the update completes with a success exit code (0); otherwise, false.</returns>
        public async Task<bool> UpdateSignaturesAsync(IProgress<string> progress)
        {
            if (!IsDefenderAvailable)
            {
                progress?.Report("Windows Defender scanner not found.");
                return false;
            }

            // Configure the process to run MpCmdRun.exe with the -SignatureUpdate argument.
            var startInfo = new ProcessStartInfo
            {
                FileName = _defenderPath,
                Arguments = "-SignatureUpdate",
                CreateNoWindow = true, // Don't show a black console window.
                WindowStyle = ProcessWindowStyle.Hidden,
                UseShellExecute = false, // Required for redirecting output.
                RedirectStandardOutput = true, // We want to capture the output text.
                StandardOutputEncoding = System.Text.Encoding.UTF8 // Ensure correct character encoding.
            };

            // The 'using' block ensures the process object is properly disposed of when done.
            using (var process = Process.Start(startInfo))
            {
                // Asynchronously read the output stream line by line as it is generated.
                // This allows the UI to show progress in real-time instead of waiting until the process is finished.
                while (!process.StandardOutput.EndOfStream)
                {
                    string line = await process.StandardOutput.ReadLineAsync();
                    if (!string.IsNullOrWhiteSpace(line))
                    {
                        // The 'progress?.Report()' safely invokes the report method on the UI thread.
                        progress?.Report(line.Trim());
                    }
                }

                // Wait for the process to fully exit. CancellationToken.None means this wait cannot be cancelled.
                await process.WaitForExitAsync(CancellationToken.None);
                // Return true only if the process exit code is 0 (success).
                return process.ExitCode == 0;
            }
        }

        /// <summary>
        /// Asynchronously scans a collection of files, managing concurrency.
        /// </summary>
        /// <param name="filePaths">A collection of full file paths to scan.</param>
        /// <param name="progress">An IProgress object to report the ScanResult of each completed scan.</param>
        /// <param name="token">A CancellationToken to allow the operation to be cancelled.</param>
        public async Task ScanFilesAsync(IEnumerable<string> filePaths, IProgress<ScanResult> progress, CancellationToken token)
        {
            // A SemaphoreSlim is a throttling mechanism. It's initialized with our maximum concurrency limit.
            // It ensures that no more than 'MaxConcurrentScans' tasks are running at any given moment.
            var semaphore = new SemaphoreSlim(MaxConcurrentScans);
            var scanTasks = new List<Task>();

            // Iterate through each file path in the list.
            foreach (var filePath in filePaths)
            {
                // Asynchronously wait until a "slot" is available in the semaphore. If all slots are
                // taken, this line will pause execution until another task calls semaphore.Release().
                await semaphore.WaitAsync(token);

                // Create and start a new Task to scan a single file.
                // Using Task.Run ensures this work is done on a background thread.
                var scanTask = Task.Run(async () =>
                {
                    // A try-finally block guarantees that semaphore.Release() is called,
                    // even if the scan encounters an error. This is crucial to prevent deadlocks.
                    try
                    {
                        // Check if cancellation has been requested by the user. If so, exit early.
                        if (token.IsCancellationRequested) return;

                        // Perform the actual scan for one file.
                        var result = await RunScanOnSingleFileAsync(filePath, token);
                        // Report the result back to the UI.
                        progress.Report(result);
                    }
                    finally
                    {
                        // Release the semaphore slot, allowing the next waiting task to proceed.
                        semaphore.Release();
                    }
                }, token);

                // Add the newly created task to our list of all running tasks.
                scanTasks.Add(scanTask);
            }

            // Asynchronously wait for ALL the scan tasks in the list to complete.
            await Task.WhenAll(scanTasks);
        }

        /// <summary>
        /// Runs the Defender command-line scan on a single file.
        /// </summary>
        /// <param name="filePath">The full path of the file to scan.</param>
        /// <param name="token">A CancellationToken to allow the operation to be cancelled.</param>
        /// <returns>A Task that resolves to a ScanResult object for the file.</returns>
        private async Task<ScanResult> RunScanOnSingleFileAsync(string filePath, CancellationToken token)
        {
            if (!IsDefenderAvailable)
            {
                return new ScanResult { FilePath = filePath, Status = ScanStatus.Error, Message = "Scanner not found" };
            }

            // Construct the command-line arguments.
            // -Scan -ScanType 3: Specifies a custom scan on a specific file.
            // -File "{filePath}": The file to scan. Quotes are important for paths with spaces.
            // -DisableRemediation: Tells Defender to only report threats, not to quarantine or remove them automatically.
            var arguments = $"-Scan -ScanType 3 -File \"{filePath}\" -DisableRemediation";
            var processStartInfo = new ProcessStartInfo
            {
                FileName = _defenderPath,
                Arguments = arguments,
                CreateNoWindow = true,
                WindowStyle = ProcessWindowStyle.Hidden,
                RedirectStandardOutput = true,
                UseShellExecute = false
            };

            try
            {
                using (var process = new Process { StartInfo = processStartInfo })
                {
                    process.Start();
                    // Asynchronously read the entire output of the process at once.
                    string output = await process.StandardOutput.ReadToEndAsync();
                    // Asynchronously wait for the process to exit, respecting the cancellation token.
                    await process.WaitForExitAsync(token);
                    // Parse the output and exit code to create a final result object.
                    return ParseDefenderOutput(filePath, process.ExitCode, output);
                }
            }
            catch (OperationCanceledException)
            {
                // This exception is thrown if the cancellation token is triggered while waiting for the process to exit.
                return new ScanResult { FilePath = filePath, Status = ScanStatus.Cancelled, Message = "CANCELLED" };
            }
            catch (Exception ex)
            {
                // Catch any other unexpected errors during process execution.
                return new ScanResult { FilePath = filePath, Status = ScanStatus.Error, Message = $"ERROR: {ex.Message}" };
            }
        }
    }
}